#include "client.h"

#include <QHostInfo>
#include <QUuid>
#include <QTimerEvent>

Client::Client(QObject *parent) //继承QObject
    :QObject(parent)
{
    QString randCode = QUuid::createUuid().toString().toUpper();   //创建一个随机码，大写
    randCode = randCode.mid(1,8);

    mName = QHostInfo::localHostName() + "_" + randCode;           //名称后增加一个随机码
    mRoom = defaultRoom;                                      //默认房间号
    mBlack = true;                                                 //默认黑子（象棋true代表红子）
    mHeartBeatTime = QDateTime::currentDateTime();                 //最近的心跳时间为当前时间

    mSocket = new QTcpSocket(this);                                              //创建通信套接字
    connect(mSocket, &QTcpSocket::connected,this,&Client::slotConnect);          //连接服务器成功会发出connected，触发槽函数
    connect(mSocket, &QTcpSocket::disconnected,this,&Client::slotDisconnect);    //断开连接时会发出disconnected，触发槽函数
    connect(mSocket, &QTcpSocket::readyRead, this, &Client::slotReadyRead);         //收到消息时发出readyRead,触发槽函数
}

void Client::GetRoomState(QList<ROOM_STATE> &state)  //接收主窗口-棋盘传来的参数，房间状态列表
{
    state = mRoomState;
}

void Client::ConnectToServer(QString ip, int port, int room) //接收主窗口-棋盘传来的参数，连接服务器
{
    mRoom = room;                           //房间号
    mSocket->connectToHost(ip, port);       //服务器ip和端口，连接服务器
}

void Client::slotConnect() //connect信号的槽函数
{
    //建立TCP连接成功，第一个消息发送登陆内容，服务器进行确认是否能登陆到指定房间
    SendMsg_Logon();                        //发送客户端登录TCP消息

    //连接成功后开始定时发心跳给服务器，保活
    mTimerHeart = startTimer(3000);         //设置心跳间隔
}

void Client::slotDisconnect() //disconnect信号的槽函数
{
    Output(QStringLiteral("与服务器的连接中断！")); //用来发送输出信号
}

void Client::slotReadyRead() //readyRead信号的槽函数
{
    int nHeaderLen = sizeof(MSG_HEADER);  //消息头的大小
    if (mSocket->bytesAvailable() <= 0) {//检查套接字接收缓冲区中当前可用的字节数
        return;
    }
    QByteArray data = mSocket->readAll(); //获取接收数据
    mData += data;                        //data加入缓存区

    while (1) {
        if (mData.size() < nHeaderLen) {
            //当前接收内容不足一个消息头长度，无法处理继续接收
            return;
        }
        MSG_HEADER* pMsg = (MSG_HEADER*)mData.data();    //创建个消息头，为刚接收的数据（消息头+附加数据）
        if (mData.size() < nHeaderLen + pMsg->nDataLen) {
            //当前接收内容足一个消息头，但不足解析本消息需要的总长度(头长度+头里指定的数据长度)
            return;
        }

        //判断是不是客户端认识的消息
        if (pMsg->nType == MA_LOGON ||
            pMsg->nType == MA_MOVE ||
            pMsg->nType == MA_MOVECC ||
            pMsg->nType == MA_START ||
            pMsg->nType == MA_SAY ||
            pMsg->nType == MA_HEARTBEAT ||
            pMsg->nType == MA_UPDATEROOMSTATE ||
            pMsg->nType == MA_OFFLINE ) {
        }
        else {
            //不认识的消息
            mData.clear();
            return;
        }

        OnMsg(pMsg, (char*)(pMsg + 1), pMsg->nDataLen);  //处理服务器发来的TCP消息

        mData.remove(0, nHeaderLen + pMsg->nDataLen);    //缓存区清空
    }
}

void Client::OnMsg(MSG_HEADER* pHeader, char *pData, int nDataLen)   //处理服务器发来的TCP消息
{
    switch ( pHeader->nType )   //不同的消息类型，执行不同操作
    {
    case MA_LOGON:              //登录回复
    {
        OnMsg_Logon(pHeader);   //处理登录TCP消息
        break;
    }
    case MA_START:              //对局开始
    {
        OnMsg_Start(pHeader);   //处理登录TCP消息
        break;
    }
    case MA_MOVE:               //服务端下发
    {
        OnMsg_Move(pHeader);    //处理执行行动落子TCP消息
        break;
    }
    case MA_MOVECC:               //服务端下发
    {
        OnMsg_MoveCC(pHeader);    //处理执行行动落子TCP消息
        break;
    }
    case MA_SAY:                //玩家说话
    {
        OnMsg_Say(pHeader);     //处理说话TCP消息
        break;
    }
    case MA_HEARTBEAT:          //心跳包
    {
        //OnMsg_Heartbeat(pHeader);
        break;
    }
    case MA_UPDATEROOMSTATE:    //服务器更新房间信息
    {
        OnMsg_UpdateRoomState(pHeader, pData, nDataLen);  //处理更新房间状态TCP消息
        break;
    }
    case MA_OFFLINE:            //断开连接
    {
        OnMsg_Offline(pHeader); //处理断开连接TCP消息
        break;
    }
    default:
    {
        break;
    }
    }
}

bool Client::SendMsg_Logon() //发送客户端登录TCP消息
{
    MSG_HEADER msg = {0};                    //创建消息头
    msg.nType = MQ_LOGON;                    //消息类型为客户端登录
    msg.nNum1 = mRoom;                       //传参房间号
    strcpy(msg.szName, mName.toLocal8Bit()); //客户端name为mName

    Send(msg);                               //发送消息头

    return true;
}

bool Client::SendMsg_Move(int x,int y) //执行行动落子
{
    MSG_HEADER msg = {0};                    //创建消息头
    msg.nType = MQ_MOVE;                     //消息类型为客户端发送走一步数据
    msg.nNum1 = x;                           //传参坐标
    msg.nNum2 = y;
    strcpy(msg.szName, mName.toLocal8Bit()); //客户端name为mName

    Send(msg);                               //发送消息头

    return true;
}

bool Client::SendMsg_MoveCC(int x1, int y1, int x2,int y2) //执行行动落子
{
    MSG_HEADER msg = {0};                    //创建消息头
    msg.nType = MQ_MOVECC;                   //消息类型为客户端发送走一步数据
    msg.nNum1 = x1;                          //传参坐标
    msg.nNum2 = y1;
    msg.nNum3 = x2;
    msg.nNum4 = y2;
    strcpy(msg.szName, mName.toLocal8Bit()); //客户端name为mName

    Send(msg);                               //发送消息头

    return true;
}

bool Client::SendMsg_Success() //通知获胜
{
    MSG_HEADER msg = {0};                     //创建消息头
    msg.nType = MQ_SUCCESS;                   //消息类型为胜利消息
    strcpy(msg.szName, mName.toLocal8Bit());  //客户端name为mName

    Send(msg);                                //发送消息头

    return true;
}

bool Client::SendMsg_Say(QString text) //接收主窗口-棋盘传来的参数，发送说话内容
{
    MSG_HEADER msg = {0};                      //创建个消息头
    msg.nType = MQ_SAY;                        //消息类型 玩家说话
    strcpy(msg.szName, mName.toLocal8Bit());   //客户端name 复制mName本地8位字符集编码的QByteArray对象
    strcpy(msg.szSay, text.toLocal8Bit());     //客户端说话内容

    Send(msg);                                 //发送消息头

    return true;
}

void Client::SendMsg_HeartBeat()  //发送心跳TCP消息
{
    MSG_HEADER msg = {0};                    //创建消息头
    msg.nType = MQ_HEARTBEAT;                //消息类型为心跳包
    strcpy(msg.szName, mName.toLocal8Bit()); //客户端name为mName

    Send(msg);                               //发送消息头
}

void Client::OnMsg_Logon(MSG_HEADER* msg) //处理登录TCP消息
{
    if (msg->nNum1 == 1) {
        QString name = QString::fromLocal8Bit(msg->szName);
        Output(QStringLiteral("%1 进入了本房间！").arg(name));

        if (msg->nNum3 == 1) {
            Output(QStringLiteral("当前房间已有1人，请再等待一位玩家进入！").arg(name));
        }
    }
    else {
        Output(QStringLiteral("登陆失败！"));
    }
}

void Client::OnMsg_Start(MSG_HEADER *pHeader) //处理开始TCP消息
{
    mBlack = (pHeader->nNum2 == 1);

    Output(QStringLiteral("房间已坐满，游戏开始！"));
    if (mRoom < 50) {  //五子棋
        if (mBlack) {
            Output(QStringLiteral("你执黑棋，请先下！"));
        }
        else {
            Output(QStringLiteral("你执白棋，等待对方黑棋先下！"));
        }
    }
    else {             //象棋
        if(mBlack) {
            Output(QStringLiteral("你执红棋，请先下！"));
        }
        else {
            Output(QStringLiteral("你执黑棋，等待对方黑棋先下！"));
        }
    }

    emit sigNewGame(); //新游戏信号
}

void Client::OnMsg_Move(MSG_HEADER* pHeader) //处理执行行动落子TCP消息
{
    emit sigOtherUserMove(pHeader->nNum1, pHeader->nNum2);
}

void Client::OnMsg_MoveCC(MSG_HEADER* pHeader) //处理执行行动落子TCP消息
{
    emit sigOtherUserMoveCC(pHeader->nNum1, pHeader->nNum2, pHeader->nNum3, pHeader->nNum4);
}

void Client::OnMsg_Say(MSG_HEADER *pHeader) //处理说话TCP消息
{
    QString name = QString::fromLocal8Bit(pHeader->szName);       //说话者的名字
    QString text = QString::fromLocal8Bit(pHeader->szSay);        //说话者说的内容

    QString msg = QStringLiteral("%1说：%2").arg(name).arg(text);
    if (text.contains(QStringLiteral("拍拍"))) {                    //包含“拍拍”，不同客户端显示不同的话
        if (name == mName) {
            msg = QStringLiteral("你 拍了拍 对方");
        }
        else {
            msg = QStringLiteral("%1 拍了拍 你").arg(name);
        }
    }
    Output(msg);
}

void Client::OnMsg_Offline(MSG_HEADER *pHeader) //处理断开连接TCP消息
{
    QString name = QString::fromLocal8Bit(pHeader->szName);
    QString msg = QStringLiteral("玩家%1断开连接……").arg(name);
    Output(msg);
}

void Client::OnMsg_UpdateRoomState(MSG_HEADER *pHeader, char *pData, int nDataLen) //处理更新房间状态TCP消息
{
    mRoomState.clear();

    //100个房间的玩家数、玩家信息
    ROOM_STATE* p = (ROOM_STATE*)(pData);
    for (int i = 0; i < maxRoom; i++) {
        mRoomState.append(*p);
        p++;
    }
    emit sigUpdateRoomState();
}

bool Client::IsBlack() //返回棋子颜色
{
    return mBlack;
}

bool Client::Send(MSG_HEADER msg)  //发简单TCP消息，只有消息头即可
{
    mSocket->write((char*)&msg,sizeof(MSG_HEADER));  //write发送
    mSocket->flush();                                //确保所有缓冲的数据都被发送
    return true;
}

bool Client::Send(MSG_HEADER msg, char *pData, int nDataLen) //给发复杂TCP消息，含消息头和附加nDataLen字节的数据
{
    int nLen = sizeof(MSG_HEADER) + nDataLen;        //长度为，消息头长度和附加消息的长度
    char* buf = new char[nLen];                      //创建个该长度的数组
    memset(buf,0,nLen);                              //这个数组初始赋0

    memcpy(buf,(char*)&msg,sizeof(MSG_HEADER));      //消息头存入新数组
    memcpy(buf + sizeof(MSG_HEADER),pData,nDataLen); //附加消息存入新数组

    mSocket->write((char*)&msg,sizeof(MSG_HEADER));  //write发送
    mSocket->flush();                                //确保所有缓冲的数据都被发送

    delete []buf;                                    //发送完，delete
    return true;
}

void Client::Output(QString text)  //用来发送输出信号
{
    emit sigOutput(text);    //发送输出信号
}

void Client::timerEvent(QTimerEvent *e) //重写定时器事件
{
    if (e->timerId() == mTimerHeart) {
        SendMsg_HeartBeat();  //定时发送心跳TCP消息
    }
}








